﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace JobDemo01.Job
{
    class JobContext(JobQueue queue, WaferMap map)
    {
        readonly JobQueue jobQueue = queue;
        readonly Channel<object> channel = Channel.CreateBounded<object>(1);

        int state = 0b10;
        int waitingForUser;

        public Guid Id { get; } = Guid.NewGuid();

        public WaferMap Map => map;

        public Task Completion => channel.Reader.Completion;

        // lifetime managed by job queue
        public MessageQueue<PatternResult> MessageQueue { get; } = new();

        public void Block(Task task)
        {
            if ((Interlocked.CompareExchange(ref state, 0, 0) & 1) == 1)
            {
                throw new InvalidOperationException("Processing has completed.");
            }

            if (!channel.Writer.TryWrite(task))
            {
                throw new InvalidOperationException("Block was not possible!");
            }
        }

        public void ManualAssist(AlignmentHint hint)
        {
            if (Interlocked.CompareExchange(ref waitingForUser, 0, 1) != 1)
            {
                throw new InvalidOperationException("Unexpected ManualAssist");
            }

            if (!channel.Writer.TryWrite(hint))
            {
                throw new InvalidOperationException("ManualAssist was not possible!");
            }
        }

        public void RequestCancellation()
        {
            Interlocked.Or(ref state, 0b01);
        }

        public void Confirm()
        {
            var s = Interlocked.CompareExchange(ref state, 0b01, 0);
            if ((s & 0b10) != 0)
            {
                throw new InvalidOperationException("Processing not finished.");
            }

            Close(null);
        }

        public async Task CheckPoint()
        {
            if ((Interlocked.CompareExchange(ref state, 0, 0) & 1) == 1)
            {
                throw new OperationCanceledException();
            }

            if (channel.Reader.TryRead(out var task))
            {
                await ((Task)task).ConfigureAwait(false);
            }
        }

        public async Task<AlignmentHint> AwaitManualAssist()
        {
            Interlocked.Exchange(ref waitingForUser, 1);
            return (AlignmentHint)await channel.Reader.ReadAsync().ConfigureAwait(false);
        }

        public void Reset()
        {
            var s = Interlocked.CompareExchange(ref state, 0b10, 0);
            if (s != 0)
            {
                throw new InvalidOperationException("Can not reset.");
            }
        }

        public void OnProcessingFinished()
        {
            Interlocked.And(ref state, 0b01);
        }

        public void Complete(Exception ex = null)
        {
            Interlocked.Exchange(ref state, 1);
            Close(ex);
        }

        void Close(Exception ex)
        {
            jobQueue.OnJobCompleted();
            channel.Writer.TryComplete(ex);
        }
    }
}
